package qc.module.platform.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import qc.common.core.enums.ColumnTypeEnum;
import qc.common.core.enums.DataTypeEnum;
import qc.common.core.enums.WhereConditionTypeEnum;
import qc.common.core.exception.QCPromptException;
import qc.common.core.json.JsonParserUtil;
import qc.common.core.pair.KeyValuePairDto;
import qc.common.core.unify.QCUnifyReturnValue;
import qc.common.core.utils.SqlRunnerUtil;
import qc.module.platform.dto.db.DBDto;
import qc.module.platform.dto.dbTable.DBTableDto;
import qc.module.platform.dto.dbTableColumn.DBTableColumnDto;
import qc.module.platform.dto.dbTableColumn.DBTableColumnOptionsDto;
import qc.module.platform.entity.QcDbTableColumn;
import qc.module.platform.mapper.DBTableColumnMapper;
import qc.module.platform.repository.QcDbTableColumnRepository;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * DbTableColumnService
 *
 * @author QuCheng Tech
 * @since 2024/1/29
 */
@Service
public class DbTableColumnService {
    private QcDbTableColumnRepository repository;
    
    @Autowired
    private DbTableService dbTableService;
    
    @Autowired
    private DbService dbService;

    @Autowired
    public void setRepository(QcDbTableColumnRepository repository) {
        this.repository = repository;
    }

    /***
     * 获取指定表的列信息，可以指定是否仅查询前端显示的列
     *
     * @param tableCode 查询的表代码
     * @param onlyShowInTableColumns 是否只查询在前端表格/图表中显示的列
     * @param sortColumn 排序列编码
     * @return java.util.List<qc.module.platform.dto.dbTableColumn.DBTableColumnDto>
     * @author QuCheng Tech
     * @since 2024/1/29
     */
    public List<DBTableColumnDto> getTableColumns(String tableCode, boolean onlyShowInTableColumns,String sortColumn) throws QCPromptException {
        //查询数据
        List<QcDbTableColumn> ens = getTableColumnEntitys(tableCode, onlyShowInTableColumns,sortColumn);
        if (ens != null && ens.size() > 0x0) {
            return DBTableColumnMapper.MAPPER.toDtoList(ens);
        }

        return null;
    }

    /***
     * 获取指定表的列信息，可以指定是否仅查询前端显示的列
     *
     * @param tableCode 查询的表代码
     * @param onlyShowInTableColumns 是否只查询在前端表格/图表中显示的列
     * @param sortColumn 排序列编码
     * @return java.util.List<qc.module.platform.dto.dbTableColumn.QcDbTableColumn>
     * @author QuCheng Tech
     * @since 2024/1/29
     */
    public List<QcDbTableColumn> getTableColumnEntitys(String tableCode, boolean onlyShowInTableColumns,String sortColumn) throws QCPromptException {
        if (StringUtils.isBlank(tableCode))
            throw new QCPromptException("查询数据库表列信息时指定的数据库表代码不能为空");

        LambdaQueryWrapper<QcDbTableColumn> wrapper = new LambdaQueryWrapper<>();
        
        //根据表代码进行筛选
        wrapper.eq(QcDbTableColumn::getTbcode, tableCode);
        if (onlyShowInTableColumns)
            wrapper.eq(QcDbTableColumn::getQrshow, true);


        wrapper.last("ORDER BY " + sortColumn);
        //排序，排序号
        //wrapper.orderByAsc(QcDbTableColumn::getCode);
        //再按名称排序
        //wrapper.orderByAsc(QcDbTableColumn::getName);

        return queryEntitys(wrapper);
    }

    /***
     * 获取显示在编辑表单中的列实体类集合
     *
     * @param tableCode 查询的表代码
     * @return java.util.List<qc.module.platform.entity.QcDbTableColumn>
     * @author QuCheng Tech
     * @since 2024/3/7
     */
    public List<QcDbTableColumn> getShowInFormColumnEntitys(String tableCode) throws QCPromptException {
        if (StringUtils.isBlank(tableCode))
            throw new QCPromptException("查询数据库表列信息时指定的数据库表代码不能为空");

        LambdaQueryWrapper<QcDbTableColumn> wrapper = new LambdaQueryWrapper<>();

        //根据表代码进行筛选
        wrapper.eq(QcDbTableColumn::getTbcode, tableCode);
        //在编辑表单中显示标记为true
        wrapper.eq(QcDbTableColumn::getEfshow, true);

        //排序，排序号
        wrapper.orderByAsc(QcDbTableColumn::getQrodr);
        //再按名称排序
        wrapper.orderByAsc(QcDbTableColumn::getName);

        return queryEntitys(wrapper);
    }

    /***
     * 获取在查询条件表单中的列实体类集合
     *
     * @param tableCode 查询的表代码
     * @return java.util.List<qc.module.platform.entity.QcDbTableColumn>
     * @author QuCheng Tech
     * @since 2024/3/7
     */
    public List<QcDbTableColumn> getShowInConditionFormColumnEntitys(String tableCode) throws QCPromptException {
        if (StringUtils.isBlank(tableCode))
            throw new QCPromptException("查询数据库表列信息时指定的数据库表代码不能为空");

        LambdaQueryWrapper<QcDbTableColumn> wrapper = new LambdaQueryWrapper<>();

        //根据表代码进行筛选
        wrapper.eq(QcDbTableColumn::getTbcode, tableCode);
        //在查询条件表单中标记为true
        wrapper.eq(QcDbTableColumn::getQcflag, true);

        //排序，排序号
        wrapper.orderByAsc(QcDbTableColumn::getQrodr);
        //再按名称排序
        wrapper.orderByAsc(QcDbTableColumn::getName);

        return queryEntitys(wrapper);
    }

    /***
     * 根据wrapper进行实体类集合查询，提供给本service中的方法公用
     *
     * @param wrapper 查询wrapper
     * @return java.util.List<qc.module.platform.entity.QcDbTableColumn>
     * @author QuCheng Tech
     * @since 2024/3/7
     */
    List<QcDbTableColumn> queryEntitys(LambdaQueryWrapper<QcDbTableColumn> wrapper) {
        if (wrapper != null) {
            //查询数据
            List<QcDbTableColumn> ens = repository.selectList(wrapper);
            if (ens != null && ens.size() > 0x0) {
                //避免出现因为列code大小写和空格的问题导致数据不能正常显示，在此从源头进行去空格和转换为小写处理
                List<QcDbTableColumn> result = new ArrayList<>();

                for (QcDbTableColumn en : ens) {
                    if (StringUtils.isNotBlank(en.getCode())) {
                        String code = en.getCode().trim().toLowerCase(Locale.ROOT);
                        en.setCode(code);

                        result.add(en);
                    }
                }
                return result;
            }
        }

        return null;
    }

    /**
     * 获取指定数据库表列信息
     * @param tableCode 数据库表编码
     * @param code 列编码
     * @return 数据库表列信息
     * @author QuCheng Tech
     * @since 2024/3/13
     */
    public DBTableColumnDto get(String tableCode,String code) throws QCPromptException {
        // 1.条件判空
        if (StringUtils.isBlank(tableCode)) throw new QCPromptException("数据库表编码不能为空");
        if (StringUtils.isBlank(code)) throw new QCPromptException("列编码不能为空");
        
        // 2.查询指定数据库表列信息
        LambdaQueryWrapper<QcDbTableColumn> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(QcDbTableColumn::getTbcode,tableCode);
        wrapper.eq(QcDbTableColumn::getCode,code);
        
        QcDbTableColumn en = repository.selectOne(wrapper);
        if (Objects.nonNull(en)) return DBTableColumnMapper.MAPPER.toDto(en);
        return null;
    }

    /**
     * 新增
     * @param dto
     * @return 成功返回null，失败返回错误信息
     * @author QuCheng Tech
     * @since 2024/3/13
     */
    public String add(DBTableColumnDto dto){
        // 1.新增对象判空
        if (Objects.isNull(dto)) return QCUnifyReturnValue.Warn("对象不能为空");
        if (StringUtils.isBlank(dto.getTbcode())) return QCUnifyReturnValue.Warn("数据库表编码不能为空");
        if (StringUtils.isBlank(dto.getCode())) return QCUnifyReturnValue.Warn("数据库表列编码不能为空");
        if (StringUtils.isBlank(dto.getName())) return QCUnifyReturnValue.Warn("数据库表列名称不能为空");
        
        // 2.判断数据库表编码对象是否存在，不存在则提示
        if (!dbTableService.isExist(dto.getTbcode())) return QCUnifyReturnValue.Warn("数据库表编码不存在");
        
        // 3.判断新增对象是否已经存在，存在则提示
        if (isExist(dto.getTbcode(),dto.getCode())) return QCUnifyReturnValue.Warn("新增的数据库表列已经存在");

        // 4.dto转换为entity
        QcDbTableColumn en = DBTableColumnMapper.MAPPER.dtoToEntity(dto);

        // 5.保存数据
        if (repository.insert(en) < 0x1)
            return QCUnifyReturnValue.Warn("新增数据失败");
        return QCUnifyReturnValue.Success();
    }
    public String update(DBTableColumnDto dto){
        // 1.修改对象判空
        if (Objects.isNull(dto)) return QCUnifyReturnValue.Warn("对象不能为空");
        if (StringUtils.isBlank(dto.getTbcode())) return QCUnifyReturnValue.Warn("数据库表编码不能为空");
        if (StringUtils.isBlank(dto.getCode())) return QCUnifyReturnValue.Warn("数据库表列编码不能为空");
        if (StringUtils.isBlank(dto.getName())) return QCUnifyReturnValue.Warn("数据库表列名称不能为空");

        // 2.判断数据库表编码对象是否存在，不存在则提示
        if (!dbTableService.isExist(dto.getTbcode())) return QCUnifyReturnValue.Warn("数据库表编码不存在");

        // 3.判断修改对象是否存在，存不在则提示
        if (!isExist(dto.getTbcode(),dto.getCode())) return QCUnifyReturnValue.Warn("修改的数据库表列不存在");

        // 4.dto转换为entity
        QcDbTableColumn en = DBTableColumnMapper.MAPPER.dtoToEntity(dto);

        // 5.修改数据
        LambdaUpdateWrapper<QcDbTableColumn> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(QcDbTableColumn::getTbcode,dto.getTbcode());
        wrapper.eq(QcDbTableColumn::getCode,dto.getCode());
        repository.update(en,wrapper);
        return QCUnifyReturnValue.Success();
    }

    /**
     * 删除
     * @param tableCode 数据库表编码
     * @param code 列编码
     * @return 成功返回null，失败返回错误信息
     * @author QuCheng Tech
     * @since 2024/3/13
     */
    public String delete(String tableCode,String code) throws QCPromptException {
        // 1.条件判空
        if (StringUtils.isBlank(tableCode)) throw new QCPromptException("数据库表编码不能为空");
        if (StringUtils.isBlank(code)) throw new QCPromptException("列编码不能为空");
        
        // 2.删除数据
        LambdaQueryWrapper<QcDbTableColumn> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(QcDbTableColumn::getTbcode,tableCode);
        wrapper.eq(QcDbTableColumn::getCode,code);
        repository.delete(wrapper);
        return QCUnifyReturnValue.Success();
    }

    /**
     * 返回数据库表列的数据选择项集合
     * @return DBTableColumnOptionsDto
     * @author QuCheng Tech
     * @since 2024/3/26
     */
    public DBTableColumnOptionsDto getOptions(){
        DBTableColumnOptionsDto optionsDto = new DBTableColumnOptionsDto();
        
        //处理列的数据类型选择项
        List<KeyValuePairDto> datatypeOptions = new ArrayList<>();
        for (DataTypeEnum dataTypeEnum : DataTypeEnum.values()){
            KeyValuePairDto keyValuePairDto = new KeyValuePairDto();
            keyValuePairDto.setKey(String.valueOf(dataTypeEnum.getIndex()));
            keyValuePairDto.setValue(dataTypeEnum.getName());
            datatypeOptions.add(keyValuePairDto);
        }
        optionsDto.setDatatypeOptions(datatypeOptions);
        
        //处理列的列类型选择项
        List<KeyValuePairDto> coltypeOptions = new ArrayList<>();
        for (ColumnTypeEnum columnTypeEnum : ColumnTypeEnum.values()){
            KeyValuePairDto keyValuePairDto = new KeyValuePairDto();
            keyValuePairDto.setKey(String.valueOf(columnTypeEnum.getIndex()));
            keyValuePairDto.setValue(columnTypeEnum.getName());
            coltypeOptions.add(keyValuePairDto);
        }
        optionsDto.setColtypeOptions(coltypeOptions);
        
        //处理查询条件比较类型选择项
        List<KeyValuePairDto> whereConditionOptions = new ArrayList<>();
        for (WhereConditionTypeEnum whereConditionTypeEnum : WhereConditionTypeEnum.values()){
            KeyValuePairDto keyValuePairDto = new KeyValuePairDto();
            keyValuePairDto.setKey(String.valueOf(whereConditionTypeEnum.getIndex()));
            keyValuePairDto.setValue(whereConditionTypeEnum.getName());
            whereConditionOptions.add(keyValuePairDto);
        }
        optionsDto.setQccmptypeOptions(whereConditionOptions);
        
        //TODO 处理字典选择项
        
        return optionsDto;
    }

    /**
     * 根据指定数据库表编码和列编码判断列信息对象是否存在
     * @param tbCode 数据库表编码
     * @param code 列编码
     * @return 返回true表示存在，false表示不存在
     * @author Qucheng Tech
     * @since 2024/3/13
     */
    private boolean isExist(String tbCode,String code){
        LambdaQueryWrapper<QcDbTableColumn> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(QcDbTableColumn::getTbcode,tbCode);
        wrapper.eq(QcDbTableColumn::getCode,code);

        if (Objects.nonNull(repository.selectOne(wrapper))) return true;
        return false;
    }

    /**
     * 获取指定表的列结构信息，处理转换后新增到数据表列中
     * 
     * @param dbTableCode 数据库表编码
     * @return 返回提示信息：共有几列，已有几列，新增几列
     * @author Qucheng Tech
     * @since 2024/04/19
     */
    public String GetAddDbTableColumnsInfo(String dbTableCode) throws QCPromptException, SQLException {
     // https://blog.csdn.net/chen_zw/article/details/18816599
     // https://blog.csdn.net/u010368839/article/details/76358831
     // https://blog.csdn.net/qq_37840993/article/details/107972781
        //返回结果提示信息
        Integer allColumnCount = 0x0; //总共查询列数
        Integer existColumnCount = 0x0; // 已有列数
        Integer addColumnCount = 0x0; //新增列数
        
        if (StringUtils.isBlank(dbTableCode)) throw new QCPromptException("数据库表编码不能为空");
        
        //根据数据库表编码获取数据库表信息
        DBTableDto dbTableDto = dbTableService.get(dbTableCode);
        if (Objects.isNull(dbTableDto)) throw new QCPromptException("数据库表信息不存在");

        //查询指定数据库表的列集合
        List<DBTableColumnDto> columnDtos = getTableColumns(dbTableCode,false,"code");
        
        //根据数据库编码获取数据库连接信息
        DBDto dbDto = dbService.get(dbTableDto.getDb());
        //数据库连接信息
        String dbUrl = "";
        String dbUser = "";
        String dbPassword = "";
        if (Objects.nonNull(dbDto)){
            //解析数据库连接信息
            String dbConJsonString =dbDto.getCon();
            if (StringUtils.isNotBlank(dbConJsonString)) {
                //Json对象进行获取
                dbUrl = JsonParserUtil.getString(dbConJsonString, "url");
                dbUser = JsonParserUtil.getString(dbConJsonString, "user");
                dbPassword = JsonParserUtil.getString(dbConJsonString, "password");
            }
        }
        
        //获取数据库连接信息
        Connection connection = SqlRunnerUtil.getConnection(dbUrl,dbUser,dbPassword);
        ResultSet resultSet = null;
        ResultSet primaryKeyResultSet = null;
        try {
            //获取指定数据库表的列信息
            /**
             * 获取可在指定类别中使用的表列的描述。
             * 方法原型:ResultSet getColumns(String catalog,String schemaPattern,String tableNamePattern,String columnNamePattern)
             * catalog - 表所在的类别(数据库)名称;""表示获取没有类别的列,null表示获取所有类别的列。
             * schema - 表所在的模式名称(oracle中对应于Tablespace);""表示获取没有模式的列,null标识获取所有模式的列; 可包含单字符通配符("_"),或多字符通配符("%");
             * tableNamePattern - 表名称;可包含单字符通配符("_"),或多字符通配符("%");
             * columnNamePattern - 列名称; ""表示获取列名为""的列(当然获取不到);null表示获取所有的列;可包含单字符通配符("_"),或多字符通配符("%");
             */
            DatabaseMetaData databaseMetaData = connection.getMetaData();
            resultSet = databaseMetaData.getColumns(connection.getCatalog(),null,dbTableCode,null);
            //获取表的主键信息
            primaryKeyResultSet = databaseMetaData.getPrimaryKeys(connection.getCatalog(), null,dbTableCode);
            
            while (resultSet.next()){
                allColumnCount += 0x1;
                String tableCode = resultSet.getString("TABLE_NAME");  //表名
                String columnCode = resultSet.getString("COLUMN_NAME");  //列名  
                String dataTypeName = resultSet.getString("TYPE_NAME");  //java.sql.Types类型名称(列类型名称)
                int columnSize = resultSet.getInt("COLUMN_SIZE");  //列大小
                //int decimalDigits = resultSet.getInt("DECIMAL_DIGITS");  //小数位数 
                //0 (columnNoNulls) - 该列不允许为空   1 (columnNullable) - 该列允许为空   2 (columnNullableUnknown) - 不确定该列是否为空
                int nullAble = resultSet.getInt("NULLABLE");  //是否允许为null  
                String remarks = resultSet.getString("REMARKS");  //列描述
                String columnDef = resultSet.getString("COLUMN_DEF");  //默认值
                
                //判断列是否已经存在，不存在则添加
                long count = 0x0;
                if (CollectionUtils.isNotEmpty(columnDtos))
                    count = columnDtos.stream().filter(columnDto -> columnDto.getCode().equals(columnCode.trim().toLowerCase(Locale.ROOT))).count();
                
                if (count > 0x0){
                    //列信息已存在
                    existColumnCount += 0x1;
                }else {
                    QcDbTableColumn column = new QcDbTableColumn();
                    column.setTbcode(tableCode);
                    column.setCode(columnCode.trim().toLowerCase(Locale.ROOT));
                    column.setDescription(remarks);
                    if (StringUtils.isNotBlank(remarks)) column.setName(remarks);
                    else column.setName(columnCode);
                    //column.setLength(columnSize);
                    column.setDefaultval(columnDef);

                    //根据列类型名称处理转换为对应的数据类型   
                    //未处理类型：TINYINT（tinyint长度为1转换为BIT）  CLOB  BLOB  LONGBLOB  MEDIUMBLOB  ARRAY  DATALINK  JAVA_OBJECT
                    if (dataTypeName.equals("CHAR") || dataTypeName.equals("VARCHAR") ||dataTypeName.equals("LONGVARCHAR")){
                        column.setDatatype(DataTypeEnum.STRING);
                    }else if (dataTypeName.equals("NUMERIC") || dataTypeName.equals("NUMERIC") ||
                            dataTypeName.equals("REAL") || dataTypeName.equals("FLOAT") || 
                            dataTypeName.equals("DOUBLE") || dataTypeName.equals("DECIMAL")){
                        column.setDatatype(DataTypeEnum.FLOAT);
                    }else if (dataTypeName.equals("BIT") || dataTypeName.equals("BOOLEAN")){
                        column.setDatatype(DataTypeEnum.BOOL);
                    }else if (dataTypeName.equals("SMALLINT") || dataTypeName.equals("INTEGER") || 
                            dataTypeName.equals("BIGINT")  || dataTypeName.equals("INT")){
                        column.setDatatype(DataTypeEnum.INT);
                    }else if (dataTypeName.equals("BINARY") || dataTypeName.equals("VARBINARY") || dataTypeName.equals("LONGVARBINARY")){
                        column.setDatatype(DataTypeEnum.BLOB);
                    }else if (dataTypeName.equals("DATE") || dataTypeName.equals("TIME") ||
                            dataTypeName.equals("DATETIME") || dataTypeName.equals("TIMESTAMP")){
                        column.setDatatype(DataTypeEnum.DATE);
                    }else {
                        column.setDatatype(DataTypeEnum.STRING);
                    }
                    
                    //设置是否为主键
                    while (primaryKeyResultSet.next()){
                        String pkColumnCode = primaryKeyResultSet.getString("COLUMN_NAME");
                        if (columnCode.equals(pkColumnCode)) column.setIsprimary(true);
                        else column.setIsprimary(false);
                    }

                    //设置非空
                    if (nullAble == 0x0) column.setNotnull(true);
                    else column.setNotnull(false);

                    //处理列类型
                    if (column.getDatatype().equals(DataTypeEnum.BOOL)){
                        column.setColtype(ColumnTypeEnum.VALUE_BOOL);
                    }else if (column.getDatatype().equals(DataTypeEnum.INT)){
                        column.setColtype(ColumnTypeEnum.VALUE_INT);
                    }else if (column.getDatatype().equals(DataTypeEnum.FLOAT)){
                        column.setColtype(ColumnTypeEnum.VALUE_DECIMAL);
                    }else if (columnCode.equals("id")){
                        column.setColtype(ColumnTypeEnum.ID);
                    }else if (columnCode.equals("id") && column.getDatatype().equals(DataTypeEnum.INT)){
                        column.setColtype(ColumnTypeEnum.ID_INT);
                    }else if (columnCode.equals("id") && column.getDatatype().equals(DataTypeEnum.INT) && columnSize == 36){
                        column.setColtype(ColumnTypeEnum.ID_UUID_36);
                    }else if (columnCode.equals("id") && column.getDatatype().equals(DataTypeEnum.INT) && columnSize == 32){
                        column.setColtype(ColumnTypeEnum.ID_UUID_32);
                    }else if (columnCode.equals("intm") && column.getDatatype().equals(DataTypeEnum.DATE)){
                        column.setColtype(ColumnTypeEnum.TIME_IN);
                    }else if (columnCode.equals("uptm") && column.getDatatype().equals(DataTypeEnum.DATE)){
                        column.setColtype(ColumnTypeEnum.TIME_UPDATE);
                    }else {
                        column.setColtype(ColumnTypeEnum.UN_DEFINE);
                    }

                    //保存列信息到数据库，新增成功则计数
                    if (repository.insert(column) > 0x0) 
                        addColumnCount += 0x1;
                }
            }
        }catch (SQLException e){
            e.printStackTrace();
        }
        return QCUnifyReturnValue.Success("共查询出" + allColumnCount + "条列信息，已有" + existColumnCount + "列，新增了" + addColumnCount + "列");
    }
}
